/* LWIP service - ndev.c - network driver communication module */
/*
 * There is almost a one-to-one mapping between network device driver (ndev)
 * objects and ethernet interface (ethif) objects, with as major difference
 * that there may be an ndev object but not an ethif object for a driver that
 * is known to exist but has not yet replied to our initialization request:
 * without the information from the initialization request, there is no point
 * creating an ethif object just yet, while we do need to track the driver
 * process.  TODO: it would be nice if unanswered init requests timed out and
 * caused the removal of the ndev object after a while.
 *
 * Beyond that, this module aims to abstract away the low-level details of
 * communication, memory grants, and driver restarts.  Driver restarts are not
 * fully transparent to the ethif module because it needs to reinitialize
 * driver state only it knows about after a restart.  Drivers that are in the
 * process of restarting and therefore not operational are said to be disabled.
 *
 * From this module's point of view, a network driver is one of two states:
 * initializing, where it has yet to respond to our initialization request, and
 * active, where it is expected to accept and respond to all other requests.
 * This module does not keep track of higher-level states and rules however;
 * that is left to the ethif layer on one side, and the network driver itself
 * on the other side.  One important example is the interface being up or down:
 * the ndev layer will happily forward send and receive requests when the
 * interface is down, but these requests will be (resp.) dropped and rejected
 * by the network driver in that state, and will not be generated by the ethif
 * layer when the layer is down.  Imposing barriers between configure and send
 * requests is also left to the other parties.
 *
 * In this module, each active network driver has a send queue and a receive
 * queue.  The send queue is shared for packet send requests and configuration
 * change requests.  The receive queue is used for packet receive requests
 * only.  Each queue has a maximum depth, which is the minimum of a value
 * provided by the network driver during initialization and local restrictions.
 * These local restrictions are different for the two queue types: the receive
 * queue is always bounded to a hardcoded value, while the send queue has a
 * guaranteed minimum depth but may use up to the driver's maximum using spare
 * entries.  For both, a minimum depth is always available, since it is not
 * possible to cancel individual send or receive requests after they have been
 * sent to a particular driver.  This does mean that we necessarily waste a
 * large number of request structures in the common case.
 *
 * The general API model does not support the notion of blocking calls.  While
 * it would make sense to retrieve e.g. error statistics from the driver only
 * when requested by userland, implementing this without threads would be
 * seriously complicated, because such requests can have many origins (ioctl,
 * PF_ROUTE message, sysctl).  Instead, we rely on drivers updating us with the
 * latest information on everything at all times, so that we can hand over a
 * cached copy of (e.g.) those error statistics right away.  We provide a means
 * for drivers to perform rate limiting of such status updates (to prevent
 * overflowing asynsend queues), by replying to these status messages.  That
 * means that there is a request-response combo going in the opposite direction
 * of the regular messages.
 *
 * TODO: in the future we will want to obtain the list of supported media modes
 * (IFM_) from drivers, so that userland can view the list.  Given the above
 * model, the easiest way would be to obtain a copy of the full list, limited
 * to a configured number of entries, at driver initialization time.  This
 * would require that the initialization request also involve a memory grant.
 *
 * If necessary, it would not be too much work to split off this module into
 * its own libndev library.  For now, there is no point in doing this and the
 * tighter coupling allows us to optimize just a little but (see pbuf usage).
 */

#include "lwip.h"
#include "ndev.h"
#include "ethif.h"

#define LABEL_MAX	16	/* FIXME: this should be in a system header */

#define NDEV_SENDQ	2	/* minimum guaranteed send queue depth */
#define NDEV_RECVQ	2	/* guaranteed receive queue depth */
#define NREQ_SPARES	8	/* spare send queue (request) objects */
#define NR_NREQ		((NDEV_SENDQ + NDEV_RECVQ) * NR_NDEV + NREQ_SPARES)

static SIMPLEQ_HEAD(, ndev_req) nreq_freelist;

static struct ndev_req {
	SIMPLEQ_ENTRY(ndev_req) nreq_next;	/* next request in queue */
	int nreq_type;				/* type of request message */
	cp_grant_id_t nreq_grant[NDEV_IOV_MAX];	/* grants for request */
} nreq_array[NR_NREQ];

static unsigned int nreq_spares;	/* number of free spare objects */

struct ndev_queue {
	uint32_t nq_head;		/* ID of oldest pending request */
	uint8_t nq_count;		/* current nr of pending requests */
	uint8_t nq_max;			/* maximum nr of pending requests */
	SIMPLEQ_HEAD(, ndev_req) nq_req; /* queue of pending requests */
};

static struct ndev {
	endpoint_t ndev_endpt;		/* driver endpoint */
	char ndev_label[LABEL_MAX];	/* driver label */
	struct ethif *ndev_ethif;	/* ethif object, or NULL if init'ing */
	struct ndev_queue ndev_sendq;	/* packet send and configure queue */
	struct ndev_queue ndev_recvq;	/* packet receive queue */
} ndev_array[NR_NDEV];

static ndev_id_t ndev_max;		/* highest driver count ever seen */

/*
 * This macro checks whether the network driver is active rather than
 * initializing.  See above for more information.
 */
#define NDEV_ACTIVE(ndev)	((ndev)->ndev_sendq.nq_max > 0)

static int ndev_pending;		/* number of initializing drivers */

/* The CTL_MINIX MINIX_LWIP "drivers" subtree.  Dynamically numbered. */
static struct rmib_node minix_lwip_drivers_table[] = {
	RMIB_INTPTR(RMIB_RO, &ndev_pending, "pending",
	    "Number of drivers currently initializing"),
};

static struct rmib_node minix_lwip_drivers_node =
    RMIB_NODE(RMIB_RO, minix_lwip_drivers_table, "drivers",
	"Network driver information");

/*
 * Initialize the network driver communication module.
 */
void
ndev_init(void)
{
	unsigned int slot;
	int r;

	/* Initialize local variables. */
	ndev_max = 0;

	SIMPLEQ_INIT(&nreq_freelist);

	for (slot = 0; slot < __arraycount(nreq_array); slot++)
		SIMPLEQ_INSERT_TAIL(&nreq_freelist, &nreq_array[slot],
		    nreq_next);

	nreq_spares = NREQ_SPARES;

	/*
	 * Preallocate the total number of grants that we could possibly need
	 * concurrently.  Even though it is extremely unlikely that we will
	 * ever need that many grants in practice, the alternative is runtime
	 * dynamic memory (re)allocation which is something we prefer to avoid
	 * altogether.  At time of writing, we end up preallocating 320 grants
	 * using up a total of a bit under 9KB of memory.
	 */
	cpf_prealloc(NR_NREQ * NDEV_IOV_MAX);


	/*
	 * Not needed, just for ultimate safety: start off all queues with
	 * wildly different request sequence numbers, to minimize the chance
	 * that any two replies will ever be confused.
	 */
	for (slot = 0; slot < __arraycount(ndev_array); slot++) {
		ndev_array[slot].ndev_sendq.nq_head = slot << 21;
		ndev_array[slot].ndev_recvq.nq_head = (slot * 2 + 1) << 20;
	}

	/* Subscribe to Data Store (DS) events from network drivers. */
	if ((r = ds_subscribe("drv\\.net\\..*",
	    DSF_INITIAL | DSF_OVERWRITE)) != OK)
		panic("unable to subscribe to driver events: %d", r);

	/*
	 * Keep track of how many drivers are in "pending" state, which means
	 * that they have not yet replied to our initialization request.
	 */
	ndev_pending = 0;

	/* Register the minix.lwip.drivers subtree. */
	mibtree_register_lwip(&minix_lwip_drivers_node);
}

/*
 * Initialize a queue for first use.
 */
static void
ndev_queue_init(struct ndev_queue * nq)
{

	/*
	 * Only ever increase sequence numbers, to minimize the chance that
	 * two (e.g. from different driver instances) happen to be the same.
	 */
	nq->nq_head++;

	nq->nq_count = 0;
	nq->nq_max = 0;
	SIMPLEQ_INIT(&nq->nq_req);
}

/*
 * Advance the given request queue, freeing up the request at the head of the
 * queue including any grants in use for it.
 */
static void
ndev_queue_advance(struct ndev_queue * nq)
{
	struct ndev_req * nreq;
	cp_grant_id_t grant;
	unsigned int i;

	nreq = SIMPLEQ_FIRST(&nq->nq_req);

	for (i = 0; i < __arraycount(nreq->nreq_grant); i++) {
		grant = nreq->nreq_grant[i];

		if (!GRANT_VALID(grant))
			break;

		/* TODO: make the safecopies code stop using errno. */
		if (cpf_revoke(grant) != 0)
			panic("unable to revoke grant: %d", -errno);
	}

	if (nreq->nreq_type != NDEV_RECV && nq->nq_count > NDEV_SENDQ) {
		nreq_spares++;

		assert(nreq_spares <= NREQ_SPARES);
	}

	SIMPLEQ_REMOVE_HEAD(&nq->nq_req, nreq_next);

	SIMPLEQ_INSERT_HEAD(&nreq_freelist, nreq, nreq_next);

	nq->nq_head++;
	nq->nq_count--;
}

/*
 * Clear any outstanding requests from the given queue and reset it to a
 * pre-initialization state.
 */
static void
ndev_queue_reset(struct ndev_queue * nq)
{

	while (nq->nq_count > 0) {
		assert(!SIMPLEQ_EMPTY(&nq->nq_req));

		ndev_queue_advance(nq);
	}

	nq->nq_max = 0;
}

/*
 * Obtain a request object for use in a new request.  Return the request
 * object, with its request type field set to 'type', and with the request
 * sequence ID returned in 'seq'.  Return NULL if no request objects are
 * available for the given request type.  If the caller does send off the
 * request, a call to ndev_queue_add() must follow immediately after.  If the
 * caller fails to send off the request for other reasons, it need not do
 * anything: this function does not perform any actions that need to be undone.
 */
static struct ndev_req *
ndev_queue_get(struct ndev_queue * nq, int type, uint32_t * seq)
{
	struct ndev_req *nreq;

	/* Has the hard queue depth limit been reached? */
	if (nq->nq_count == nq->nq_max)
		return NULL;

	/*
	 * For send requests, we may use request objects from a shared "spares"
	 * pool, if available.
	 */
	if (type != NDEV_RECV && nq->nq_count >= NDEV_SENDQ &&
	    nreq_spares == 0)
		return NULL;

	assert(!SIMPLEQ_EMPTY(&nreq_freelist));
	nreq = SIMPLEQ_FIRST(&nreq_freelist);

	nreq->nreq_type = type;

	*seq = nq->nq_head + nq->nq_count;

	return nreq;
}

/*
 * Add a successfully sent request to the given queue.  The request must have
 * been obtained using ndev_queue_get() directly before the call to this
 * function.  This function never fails.
 */
static void
ndev_queue_add(struct ndev_queue * nq, struct ndev_req * nreq)
{

	if (nreq->nreq_type != NDEV_RECV && nq->nq_count >= NDEV_SENDQ) {
		assert(nreq_spares > 0);

		nreq_spares--;
	}

	SIMPLEQ_REMOVE_HEAD(&nreq_freelist, nreq_next);

	SIMPLEQ_INSERT_TAIL(&nq->nq_req, nreq, nreq_next);

	nq->nq_count++;
}

/*
 * Remove the head of the given request queue, but only if it matches the given
 * request type and sequence ID.  Return TRUE if the head was indeed removed,
 * or FALSE if the head of the request queue (if any) did not match the given
 * type and/or sequence ID.
 */
static int
ndev_queue_remove(struct ndev_queue * nq, int type, uint32_t seq)
{
	struct ndev_req *nreq;

	if (nq->nq_count < 1 || nq->nq_head != seq)
		return FALSE;

	assert(!SIMPLEQ_EMPTY(&nq->nq_req));
	nreq = SIMPLEQ_FIRST(&nq->nq_req);

	if (nreq->nreq_type != type)
		return FALSE;

	ndev_queue_advance(nq);

	return TRUE;
}

/*
 * Send an initialization request to a driver.  If this is a new driver, the
 * ethif module does not get to know about the driver until it answers to this
 * request, as the ethif module needs much of what the reply contains.  On the
 * other hand, if this is a restarted driver, it will stay disabled until the
 * init reply comes in.
 */
static void
ndev_send_init(struct ndev * ndev)
{
	message m;
	int r;

	memset(&m, 0, sizeof(m));
	m.m_type = NDEV_INIT;
	m.m_ndev_netdriver_init.id = ndev->ndev_sendq.nq_head;

	if ((r = asynsend3(ndev->ndev_endpt, &m, AMF_NOREPLY)) != OK)
		panic("asynsend to driver failed: %d", r);
}

/*
 * A network device driver has been started or restarted.
 */
static void
ndev_up(const char * label, endpoint_t endpt)
{
	static int reported = FALSE;
	struct ndev *ndev;
	ndev_id_t slot;

	/*
	 * First see if we already had an entry for this driver.  If so, it has
	 * been restarted, and we need to report it as not running to ethif.
	 */
	ndev = NULL;

	for (slot = 0; slot < ndev_max; slot++) {
		if (ndev_array[slot].ndev_endpt == NONE) {
			if (ndev == NULL)
				ndev = &ndev_array[slot];

			continue;
		}

		if (!strcmp(ndev_array[slot].ndev_label, label)) {
			/* Cancel any ongoing requests. */
			ndev_queue_reset(&ndev_array[slot].ndev_sendq);
			ndev_queue_reset(&ndev_array[slot].ndev_recvq);

			if (ndev_array[slot].ndev_ethif != NULL) {
				ethif_disable(ndev_array[slot].ndev_ethif);

				ndev_pending++;
			}

			ndev_array[slot].ndev_endpt = endpt;

			/* Attempt to resume communication. */
			ndev_send_init(&ndev_array[slot]);

			return;
		}
	}

	if (ndev == NULL) {
		/*
		 * If there is no free slot for this driver in our table, we
		 * necessarily have to ignore the driver altogether.  We report
		 * such cases once, so that the user can recompile if desired.
		 */
		if (ndev_max == __arraycount(ndev_array)) {
			if (!reported) {
				printf("LWIP: not enough ndev slots!\n");

				reported = TRUE;
			}
			return;
		}

		ndev = &ndev_array[ndev_max++];
	}

	/* Initialize the slot. */
	ndev->ndev_endpt = endpt;
	strlcpy(ndev->ndev_label, label, sizeof(ndev->ndev_label));
	ndev->ndev_ethif = NULL;
	ndev_queue_init(&ndev->ndev_sendq);
	ndev_queue_init(&ndev->ndev_recvq);

	ndev_send_init(ndev);

	ndev_pending++;
}

/*
 * A network device driver has been terminated.
 */
static void
ndev_down(struct ndev * ndev)
{

	/* Cancel any ongoing requests. */
	ndev_queue_reset(&ndev->ndev_sendq);
	ndev_queue_reset(&ndev->ndev_recvq);

	/*
	 * If this ndev object had a corresponding ethif object, tell the ethif
	 * layer that the device is really gone now.
	 */
	if (ndev->ndev_ethif != NULL)
		ethif_remove(ndev->ndev_ethif);
	else
		ndev_pending--;

	/* Remove the driver from our own administration. */
	ndev->ndev_endpt = NONE;

	while (ndev_max > 0 && ndev_array[ndev_max - 1].ndev_endpt == NONE)
		ndev_max--;
}

/*
 * The DS service has notified us of changes to our subscriptions.  That means
 * that network drivers may have been started, restarted, and/or shut down.
 * Find out what has changed, and act accordingly.
 */
void
ndev_check(void)
{
	static const char *prefix = "drv.net.";
	char key[DS_MAX_KEYLEN], *label;
	size_t prefixlen;
	endpoint_t endpt;
	uint32_t val;
	ndev_id_t slot;
	int r;

	prefixlen = strlen(prefix);

	/* Check whether any drivers have been (re)started. */
	while ((r = ds_check(key, NULL, &endpt)) == OK) {
		if (strncmp(key, prefix, prefixlen) != 0 || endpt == NONE)
			continue;

		if (ds_retrieve_u32(key, &val) != OK || val != DS_DRIVER_UP)
			continue;

		label = &key[prefixlen];
		if (label[0] == '\0' || memchr(label, '\0', LABEL_MAX) == NULL)
			continue;

		ndev_up(label, endpt);
	}

	if (r != ENOENT)
		printf("LWIP: DS check failed (%d)\n", r);

	/*
	 * Check whether the drivers we currently know about are still up.  The
	 * ones that are not are really gone.  It is no problem that we recheck
	 * any drivers that have just been reported by ds_check() above.
	 * However, we cannot check the same key: while the driver is being
	 * restarted, its driver status is already gone from DS. Instead, see
	 * if there is still an entry for its label, as that entry remains in
	 * existence during the restart.  The associated endpoint may still
	 * change however, so do not check that part: in such cases we will get
	 * a driver-up announcement later anyway.
	 */
	for (slot = 0; slot < ndev_max; slot++) {
		if (ndev_array[slot].ndev_endpt == NONE)
			continue;

		if (ds_retrieve_label_endpt(ndev_array[slot].ndev_label,
		    &endpt) != OK)
			ndev_down(&ndev_array[slot]);
	}
}

/*
 * A network device driver has sent a reply to our initialization request.
 */
static void
ndev_init_reply(struct ndev * ndev, const message * m_ptr)
{
	struct ndev_hwaddr hwaddr;
	uint8_t hwaddr_len, max_send, max_recv;
	const char *name;
	int enabled;

	/*
	 * Make sure that we were waiting for a reply to an initialization
	 * request, and that this is the reply to that request.
	 */
	if (NDEV_ACTIVE(ndev) ||
	    m_ptr->m_netdriver_ndev_init_reply.id != ndev->ndev_sendq.nq_head)
		return;

	/*
	 * Do just enough sanity checking on the data to pass it up to the
	 * ethif layer, which will check the rest (e.g., name duplicates).
	 */
	if (memchr(m_ptr->m_netdriver_ndev_init_reply.name, '\0',
	    sizeof(m_ptr->m_netdriver_ndev_init_reply.name)) == NULL ||
	    m_ptr->m_netdriver_ndev_init_reply.name[0] == '\0') {
		printf("LWIP: driver %d provided invalid name\n",
		    m_ptr->m_source);

		ndev_down(ndev);

		return;
	}

	hwaddr_len = m_ptr->m_netdriver_ndev_init_reply.hwaddr_len;
	if (hwaddr_len < 1 || hwaddr_len > __arraycount(hwaddr.nhwa_addr)) {
		printf("LWIP: driver %d provided invalid HW-addr length\n",
		    m_ptr->m_source);

		ndev_down(ndev);

		return;
	}

	if ((max_send = m_ptr->m_netdriver_ndev_init_reply.max_send) < 1 ||
	    (max_recv = m_ptr->m_netdriver_ndev_init_reply.max_recv) < 1) {
		printf("LWIP: driver %d provided invalid queue maximum\n",
		    m_ptr->m_source);

		ndev_down(ndev);

		return;
	}

	/*
	 * If the driver is new, allocate a new ethif object for it.  On
	 * success, or if the driver was restarted, (re)enable the interface.
	 * Both calls may fail, in which case we should forget about the
	 * driver.  It may continue to send us messages, which we should then
	 * discard.
	 */
	name = m_ptr->m_netdriver_ndev_init_reply.name;

	if (ndev->ndev_ethif == NULL) {
		ndev->ndev_ethif = ethif_add((ndev_id_t)(ndev - ndev_array),
		    name, m_ptr->m_netdriver_ndev_init_reply.caps);
		name = NULL;
	}

	if (ndev->ndev_ethif != NULL) {
		/*
		 * Set the maximum numbers of pending requests (for each
		 * direction) first, because enabling the interface may cause
		 * the ethif layer to start sending requests immediately.
		 */
		ndev->ndev_sendq.nq_max = max_send;
		ndev->ndev_sendq.nq_head++;

		/*
		 * Limit the maximum number of concurrently pending receive
		 * requests to our configured maximum.  For send requests, we
		 * use a more dynamic approach with spare request objects.
		 */
		if (max_recv > NDEV_RECVQ)
			max_recv = NDEV_RECVQ;
		ndev->ndev_recvq.nq_max = max_recv;
		ndev->ndev_recvq.nq_head++;

		memset(&hwaddr, 0, sizeof(hwaddr));
		memcpy(hwaddr.nhwa_addr,
		    m_ptr->m_netdriver_ndev_init_reply.hwaddr, hwaddr_len);

		/*
		 * Provide a NULL pointer for the name if we have only just
		 * added the interface at all.  The callee may use this to
		 * determine whether the driver is new or has been restarted.
		 */
		enabled = ethif_enable(ndev->ndev_ethif, name, &hwaddr,
		    m_ptr->m_netdriver_ndev_init_reply.hwaddr_len,
		    m_ptr->m_netdriver_ndev_init_reply.caps,
		    m_ptr->m_netdriver_ndev_init_reply.link,
		    m_ptr->m_netdriver_ndev_init_reply.media);
	} else
		enabled = FALSE;

	/*
	 * If we did not manage to enable the interface, remove it again,
	 * possibly also from the ethif layer.
	 */
	if (!enabled)
		ndev_down(ndev);
	else
		ndev_pending--;
}

/*
 * Request that a network device driver change its configuration.  This
 * function allows for configuration of various different driver and device
 * aspects: the I/O mode (and multicast receipt list), the enabled (sub)set of
 * capabilities, the driver-specific flags, and the hardware address.  Each of
 * these settings may be changed by setting the corresponding NDEV_SET_ flag in
 * the 'set' field of the given configuration structure.  It is explicitly
 * allowed to generate a request with no NDEV_SET_ flags; such a request will
 * be sent to the driver and ultimately generate a response.  Return OK if the
 * configuration request was sent to the driver, EBUSY if no (more) requests
 * can be sent to the driver right now, or ENOMEM on grant allocation failure.
 */
int
ndev_conf(ndev_id_t id, const struct ndev_conf * nconf)
{
	struct ndev *ndev;
	struct ndev_req *nreq;
	uint32_t seq;
	message m;
	cp_grant_id_t grant;
	int r;

	assert(id < __arraycount(ndev_array));
	ndev = &ndev_array[id];

	assert(ndev->ndev_endpt != NONE);
	assert(NDEV_ACTIVE(ndev));

	if ((nreq = ndev_queue_get(&ndev->ndev_sendq, NDEV_CONF,
	    &seq)) == NULL)
		return EBUSY;

	memset(&m, 0, sizeof(m));
	m.m_type = NDEV_CONF;
	m.m_ndev_netdriver_conf.id = seq;
	m.m_ndev_netdriver_conf.set = nconf->nconf_set;

	grant = GRANT_INVALID;

	if (nconf->nconf_set & NDEV_SET_MODE) {
		m.m_ndev_netdriver_conf.mode = nconf->nconf_mode;

		if (nconf->nconf_mode & NDEV_MODE_MCAST_LIST) {
			assert(nconf->nconf_mclist != NULL);
			assert(nconf->nconf_mccount != 0);

			grant = cpf_grant_direct(ndev->ndev_endpt,
			    (vir_bytes)nconf->nconf_mclist,
			    sizeof(nconf->nconf_mclist[0]) *
			    nconf->nconf_mccount, CPF_READ);

			if (!GRANT_VALID(grant))
				return ENOMEM;

			m.m_ndev_netdriver_conf.mcast_count =
			    nconf->nconf_mccount;
		}
	}

	m.m_ndev_netdriver_conf.mcast_grant = grant;

	if (nconf->nconf_set & NDEV_SET_CAPS)
		m.m_ndev_netdriver_conf.caps = nconf->nconf_caps;

	if (nconf->nconf_set & NDEV_SET_FLAGS)
		m.m_ndev_netdriver_conf.flags = nconf->nconf_flags;

	if (nconf->nconf_set & NDEV_SET_MEDIA)
		m.m_ndev_netdriver_conf.media = nconf->nconf_media;

	if (nconf->nconf_set & NDEV_SET_HWADDR)
		memcpy(m.m_ndev_netdriver_conf.hwaddr,
		    nconf->nconf_hwaddr.nhwa_addr,
		    __arraycount(m.m_ndev_netdriver_conf.hwaddr));

	if ((r = asynsend3(ndev->ndev_endpt, &m, AMF_NOREPLY)) != OK)
		panic("asynsend to driver failed: %d", r);

	nreq->nreq_grant[0] = grant; /* may also be invalid */
	nreq->nreq_grant[1] = GRANT_INVALID;

	ndev_queue_add(&ndev->ndev_sendq, nreq);

	return OK;
}

/*
 * The network device driver has sent a reply to a configuration request.
 */
static void
ndev_conf_reply(struct ndev * ndev, const message * m_ptr)
{

	/*
	 * Was this the request we were waiting for?  If so, remove it from the
	 * send queue.  Otherwise, ignore this reply message.
	 */
	if (!NDEV_ACTIVE(ndev) || !ndev_queue_remove(&ndev->ndev_sendq,
	    NDEV_CONF, m_ptr->m_netdriver_ndev_reply.id))
		return;

	/* Tell the ethif layer about the updated configuration. */
	assert(ndev->ndev_ethif != NULL);

	ethif_configured(ndev->ndev_ethif,
	    m_ptr->m_netdriver_ndev_reply.result);
}

/*
 * Construct a packet send or receive request and send it off to a network
 * driver.  The given pbuf chain may be part of a queue.  Return OK if the
 * request was successfully sent, or ENOMEM on grant allocation failure.
 */
static int
ndev_transfer(struct ndev * ndev, const struct pbuf * pbuf, int do_send,
	uint32_t seq, struct ndev_req * nreq)
{
	cp_grant_id_t grant;
	message m;
	unsigned int i;
	size_t left;
	int r;

	memset(&m, 0, sizeof(m));
	m.m_type = (do_send) ? NDEV_SEND : NDEV_RECV;
	m.m_ndev_netdriver_transfer.id = seq;

	left = pbuf->tot_len;

	for (i = 0; left > 0; i++) {
		assert(i < NDEV_IOV_MAX);

		grant = cpf_grant_direct(ndev->ndev_endpt,
		    (vir_bytes)pbuf->payload, pbuf->len,
		    (do_send) ? CPF_READ : CPF_WRITE);

		if (!GRANT_VALID(grant)) {
			while (i-- > 0)
				(void)cpf_revoke(nreq->nreq_grant[i]);

			return ENOMEM;
		}

		m.m_ndev_netdriver_transfer.grant[i] = grant;
		m.m_ndev_netdriver_transfer.len[i] = pbuf->len;

		nreq->nreq_grant[i] = grant;

		assert(left >= pbuf->len);
		left -= pbuf->len;
		pbuf = pbuf->next;
	}

	m.m_ndev_netdriver_transfer.count = i;

	/*
	 * Unless the array is full, an invalid grant marks the end of the list
	 * of invalid grants.
	 */
	if (i < __arraycount(nreq->nreq_grant))
		nreq->nreq_grant[i] = GRANT_INVALID;

	if ((r = asynsend3(ndev->ndev_endpt, &m, AMF_NOREPLY)) != OK)
		panic("asynsend to driver failed: %d", r);

	return OK;
}

/*
 * Send a packet to the given network driver.  Return OK if the packet is sent
 * off to the driver, EBUSY if no (more) packets can be sent to the driver at
 * this time, or ENOMEM on grant allocation failure.
 *
 * The use of 'pbuf' in this interface is a bit ugly, but it saves us from
 * having to go through an intermediate representation (e.g. an iovec array)
 * for the data being sent.  The same applies to ndev_receive().
 */
int
ndev_send(ndev_id_t id, const struct pbuf * pbuf)
{
	struct ndev *ndev;
	struct ndev_req *nreq;
	uint32_t seq;
	int r;

	assert(id < __arraycount(ndev_array));
	ndev = &ndev_array[id];

	assert(ndev->ndev_endpt != NONE);
	assert(NDEV_ACTIVE(ndev));

	if ((nreq = ndev_queue_get(&ndev->ndev_sendq, NDEV_SEND,
	    &seq)) == NULL)
		return EBUSY;

	if ((r = ndev_transfer(ndev, pbuf, TRUE /*do_send*/, seq, nreq)) != OK)
		return r;

	ndev_queue_add(&ndev->ndev_sendq, nreq);

	return OK;
}

/*
 * The network device driver has sent a reply to a send request.
 */
static void
ndev_send_reply(struct ndev * ndev, const message * m_ptr)
{

	/*
	 * Was this the request we were waiting for?  If so, remove it from the
	 * send queue.  Otherwise, ignore this reply message.
	 */
	if (!NDEV_ACTIVE(ndev) || !ndev_queue_remove(&ndev->ndev_sendq,
	    NDEV_SEND, m_ptr->m_netdriver_ndev_reply.id))
		return;

	/* Tell the ethif layer about the result of the transmission. */
	assert(ndev->ndev_ethif != NULL);

	ethif_sent(ndev->ndev_ethif,
	    m_ptr->m_netdriver_ndev_reply.result);
}

/*
 * Return TRUE if a new receive request can be spawned for a particular network
 * driver, or FALSE if its queue of receive requests is full.  This call exists
 * merely to avoid needless buffer allocatin in the case that ndev_recv() is
 * going to return EBUSY anyway.
 */
int
ndev_can_recv(ndev_id_t id)
{
	struct ndev *ndev;

	assert(id < __arraycount(ndev_array));
	ndev = &ndev_array[id];

	assert(ndev->ndev_endpt != NONE);
	assert(NDEV_ACTIVE(ndev));

	return (ndev->ndev_recvq.nq_count < ndev->ndev_recvq.nq_max);
}

/*
 * Start the process of receiving a packet from a network driver.  The packet
 * will be stored in the given pbuf chain upon completion.  Return OK if the
 * receive request is sent to the driver, EBUSY if the maximum number of
 * concurrent receive requests has been reached for this driver, or ENOMEM on
 * grant allocation failure.
 */
int
ndev_recv(ndev_id_t id, struct pbuf * pbuf)
{
	struct ndev *ndev;
	struct ndev_req *nreq;
	uint32_t seq;
	int r;

	assert(id < __arraycount(ndev_array));
	ndev = &ndev_array[id];

	assert(ndev->ndev_endpt != NONE);
	assert(NDEV_ACTIVE(ndev));

	if ((nreq = ndev_queue_get(&ndev->ndev_recvq, NDEV_RECV,
	    &seq)) == NULL)
		return EBUSY;

	if ((r = ndev_transfer(ndev, pbuf, FALSE /*do_send*/, seq,
	    nreq)) != OK)
		return r;

	ndev_queue_add(&ndev->ndev_recvq, nreq);

	return OK;
}

/*
 * The network device driver has sent a reply to a receive request.
 */
static void
ndev_recv_reply(struct ndev * ndev, const message * m_ptr)
{

	/*
	 * Was this the request we were waiting for?  If so, remove it from the
	 * receive queue.  Otherwise, ignore this reply message.
	 */
	if (!NDEV_ACTIVE(ndev) || !ndev_queue_remove(&ndev->ndev_recvq,
	    NDEV_RECV, m_ptr->m_netdriver_ndev_reply.id))
		return;

	/* Tell the ethif layer about the result of the receipt. */
	assert(ndev->ndev_ethif != NULL);

	ethif_received(ndev->ndev_ethif,
	    m_ptr->m_netdriver_ndev_reply.result);
}

/*
 * A network device driver sent a status report to us.  Process it and send a
 * reply.
 */
static void
ndev_status(struct ndev * ndev, const message * m_ptr)
{
	message m;
	int r;

	if (!NDEV_ACTIVE(ndev))
		return;

	/* Tell the ethif layer about the status update. */
	assert(ndev->ndev_ethif != NULL);

	ethif_status(ndev->ndev_ethif, m_ptr->m_netdriver_ndev_status.link,
	    m_ptr->m_netdriver_ndev_status.media,
	    m_ptr->m_netdriver_ndev_status.oerror,
	    m_ptr->m_netdriver_ndev_status.coll,
	    m_ptr->m_netdriver_ndev_status.ierror,
	    m_ptr->m_netdriver_ndev_status.iqdrop);

	/*
	 * Send a reply, so that the driver knows it can send a new status
	 * update without risking asynsend queue overflows.  The ID of these
	 * messages is chosen by the driver and and we simply echo it.
	 */
	memset(&m, 0, sizeof(m));
	m.m_type = NDEV_STATUS_REPLY;
	m.m_ndev_netdriver_status_reply.id = m_ptr->m_netdriver_ndev_status.id;

	if ((r = asynsend(m_ptr->m_source, &m)) != OK)
		panic("asynsend to driver failed: %d", r);
}

/*
 * Process a network driver reply message.
 */
void
ndev_process(const message * m_ptr, int ipc_status)
{
	struct ndev *ndev;
	endpoint_t endpt;
	ndev_id_t slot;

	/* Find the slot of the driver that sent the message, if any. */
	endpt = m_ptr->m_source;

	for (slot = 0, ndev = ndev_array; slot < ndev_max; slot++, ndev++)
		if (ndev->ndev_endpt == endpt)
			break;

	/*
	 * If we cannot find a slot for the driver, drop the message.  We may
	 * be ignoring the driver because it misbehaved or we are out of slots.
	 */
	if (slot == ndev_max)
		return;

	/*
	 * Process the reply message.  For future compatibility, ignore any
	 * unrecognized message types.
	 */
	switch (m_ptr->m_type) {
	case NDEV_INIT_REPLY:
		ndev_init_reply(ndev, m_ptr);

		break;

	case NDEV_CONF_REPLY:
		ndev_conf_reply(ndev, m_ptr);

		break;

	case NDEV_SEND_REPLY:
		ndev_send_reply(ndev, m_ptr);

		break;

	case NDEV_RECV_REPLY:
		ndev_recv_reply(ndev, m_ptr);

		break;

	case NDEV_STATUS:
		ndev_status(ndev, m_ptr);

		break;
	}
}
